#include "init_opencl.hpp"
#include "matrix_operations.hpp"
#include <time.h>
#include <fstream>
#include <iostream>
#include <CL/cl.hpp>

float * findProductCooOCL(OpenCLEnv openCLEnv, MatrixOperations matrix, MatrixOperations v, int nrTimes) {
	clock_t init, final;
	int rows = matrix.getNumRows();
	int columns = matrix.getNumCols();
	int nonZeros = matrix.getNumNonZeros();
	float * mValues = matrix.getCooPackValues();
	float * vValues = v.getMatrix();
	int * rowIdx = matrix.getCooPackRowIdx();
	int * colIdx = matrix.getCooPackColIdx();

	float * answer = new float[rows];

	cl::Context context = openCLEnv.getContext();
	cl::CommandQueue queue = openCLEnv.getQueue();

	// Read source file
	ifstream sourceFile("coo_opencl.cl");
	string sourceCode(
		istreambuf_iterator<char>(sourceFile),
		(istreambuf_iterator<char>()));
	cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));

	// Make program of the source code in the context
	cl::Program program = cl::Program(context, source);

	// Build program for these specific devices
	program.build(openCLEnv.getDevices());

	// Make kernel
	cl::Kernel kernel(program, "coo_opencl");


	cl::Buffer bufferValues = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(float));
	cl::Buffer bufferVector = cl::Buffer(context, CL_MEM_READ_ONLY, columns * sizeof(float));
	cl::Buffer bufferRowIdx = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(int));
	cl::Buffer bufferColIdx = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(int));
	cl::Buffer bufferAnswer = cl::Buffer(context, CL_MEM_WRITE_ONLY, rows * sizeof(float));

	queue.enqueueWriteBuffer(bufferValues, CL_TRUE, 0, nonZeros * sizeof(float), mValues);
	queue.enqueueWriteBuffer(bufferVector, CL_TRUE, 0, columns * sizeof(float), vValues);
	queue.enqueueWriteBuffer(bufferRowIdx, CL_TRUE, 0, nonZeros * sizeof(int), rowIdx);
	queue.enqueueWriteBuffer(bufferColIdx, CL_TRUE, 0, nonZeros * sizeof(int), colIdx);

	kernel.setArg(0, bufferValues);
	kernel.setArg(1, bufferVector);
	kernel.setArg(2, bufferRowIdx);
	kernel.setArg(3, bufferColIdx);
	kernel.setArg(4, nonZeros);
	kernel.setArg(5, bufferAnswer);

	cl::NDRange global(nonZeros);
	cl::NDRange local(1);

	init=clock();
	for(int i = 0; i < nrTimes; i++) {
		queue.enqueueNDRangeKernel(kernel, cl::NullRange, global, local);
		queue.enqueueReadBuffer(bufferAnswer, CL_TRUE, 0, rows * sizeof(float), answer);
	}
	final=clock()-init;

	double time = (double)final / ((double)CLOCKS_PER_SEC);

	cout << "Calculation took: " << time << " seconds. (COO OpenCL, running kernel)" << endl;
    cout << "COO opencl, time per: " << time/nrTimes << endl;
	return answer;
}
